use crate::domain::user::User;
use async_trait::async_trait;
use libnftp::storage::{self, Error, Fileinfo, Metadata, StorageBackend};
use std::fmt::Debug;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use tokio::io::{AsyncRead, AsyncWrite};

#[derive(Debug)]
pub struct ChoosingVfs {
    pub inner: InnerVfs,
}

#[derive(Debug)]
pub enum InnerVfs {
    File(nftp_sbe_fs::Filesystem),
}

#[derive(Debug)]
pub enum SbeMeta {
    File(nftp_sbe_fs::Meta),
}

impl Metadata for SbeMeta {
    fn len(&self) -> u64 {
        match self {
            SbeMeta::File(m) => m.len(),
        }
    }

    fn is_dir(&self) -> bool {
        match self {
            SbeMeta::File(m) => m.is_dir(),
        }
    }

    fn is_file(&self) -> bool {
        match self {
            SbeMeta::File(m) => m.is_file(),
        }
    }

    fn is_symlink(&self) -> bool {
        match self {
            SbeMeta::File(m) => m.is_symlink(),
        }
    }

    fn modified(&self) -> storage::Result<SystemTime> {
        match self {
            SbeMeta::File(m) => m.modified(),
        }
    }

    fn gid(&self) -> u32 {
        match self {
            SbeMeta::File(m) => m.gid(),
        }
    }

    fn uid(&self) -> u32 {
        match self {
            SbeMeta::File(m) => m.uid(),
        }
    }
}

#[async_trait]
impl StorageBackend<User> for ChoosingVfs {
    type Metadata = SbeMeta;

    fn name(&self) -> &str {
        match &self.inner {
            InnerVfs::File(i) => StorageBackend::<User>::name(i),
        }
    }

    fn supported_features(&self) -> u32 {
        match &self.inner {
            InnerVfs::File(i) => StorageBackend::<User>::supported_features(i),
        }
    }

    async fn metadata<P: AsRef<Path> + Send + Debug>(
        &self,
        user: &User,
        path: P,
    ) -> storage::Result<Self::Metadata> {
        match &self.inner {
            InnerVfs::File(i) => i.metadata(user, path).await.map(SbeMeta::File),
        }
    }

    async fn list<P: AsRef<Path> + Send + Debug>(
        &self,
        user: &User,
        path: P,
    ) -> storage::Result<Vec<Fileinfo<PathBuf, Self::Metadata>>>
    where
        <Self as StorageBackend<User>>::Metadata: Metadata,
    {
        match &self.inner {
            InnerVfs::File(i) => i.list(user, path).await.map(|v| {
                v.into_iter()
                    .map(|fi| Fileinfo {
                        path: fi.path,
                        metadata: SbeMeta::File(fi.metadata),
                    })
                    .collect()
            }),
        }
    }

    async fn list_fmt<P>(&self, user: &User, path: P) -> Result<Cursor<Vec<u8>>, Error>
    where
        P: AsRef<Path> + Send + Debug,
        Self::Metadata: Metadata + 'static,
    {
        match &self.inner {
            InnerVfs::File(i) => i.list_fmt(user, path).await,
        }
    }

    async fn nlst<P>(&self, user: &User, path: P) -> Result<Cursor<Vec<u8>>, std::io::Error>
    where
        P: AsRef<Path> + Send + Debug,
        Self::Metadata: Metadata + 'static,
    {
        match &self.inner {
            InnerVfs::File(i) => i.nlst(user, path).await,
        }
    }

    async fn get_into<'a, P, W: ?Sized>(
        &self,
        user: &User,
        path: P,
        start_pos: u64,
        output: &'a mut W,
    ) -> storage::Result<u64>
    where
        W: AsyncWrite + Unpin + Sync + Send,
        P: AsRef<Path> + Send + Debug,
    {
        match &self.inner {
            InnerVfs::File(i) => i.get_into(user, path, start_pos, output).await,
        }
    }

    async fn get<P: AsRef<Path> + Send + Debug>(
        &self,
        user: &User,
        path: P,
        start_pos: u64,
    ) -> storage::Result<Box<dyn AsyncRead + Send + Sync + Unpin>> {
        match &self.inner {
            InnerVfs::File(i) => i.get(user, path, start_pos).await,
        }
    }

    async fn put<P: AsRef<Path> + Send + Debug, R: AsyncRead + Send + Sync + Unpin + 'static>(
        &self,
        user: &User,
        input: R,
        path: P,
        start_pos: u64,
    ) -> storage::Result<u64> {
        match &self.inner {
            InnerVfs::File(i) => i.put(user, input, path, start_pos).await,
        }
    }

    async fn del<P: AsRef<Path> + Send + Debug>(
        &self,
        user: &User,
        path: P,
    ) -> storage::Result<()> {
        match &self.inner {
            InnerVfs::File(i) => i.del(user, path).await,
        }
    }

    async fn mkd<P: AsRef<Path> + Send + Debug>(
        &self,
        user: &User,
        path: P,
    ) -> storage::Result<()> {
        match &self.inner {
            InnerVfs::File(i) => i.mkd(user, path).await,
        }
    }

    async fn rename<P: AsRef<Path> + Send + Debug>(
        &self,
        user: &User,
        from: P,
        to: P,
    ) -> storage::Result<()> {
        match &self.inner {
            InnerVfs::File(i) => i.rename(user, from, to).await,
        }
    }

    async fn rmd<P: AsRef<Path> + Send + Debug>(
        &self,
        user: &User,
        path: P,
    ) -> storage::Result<()> {
        match &self.inner {
            InnerVfs::File(i) => i.rmd(user, path).await,
        }
    }

    async fn cwd<P: AsRef<Path> + Send + Debug>(
        &self,
        user: &User,
        path: P,
    ) -> storage::Result<()> {
        match &self.inner {
            InnerVfs::File(i) => i.cwd(user, path).await,
        }
    }
}
